package com.lin.blog.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.lin.blog.constant.ArticleCategoryTagsConstant;
import com.lin.blog.constant.ElasticSearchConstant;
import com.lin.blog.constant.UserConstant;
import com.lin.blog.mapper.ArticleCategoryTagsMapper;
import com.lin.blog.repository.ArticleCategoryTagsRepository;
import com.lin.blog.service.IElasticSearchService;
import com.lin.blog.vo.ArticleCategoryTagsVO;
import com.lin.constant.ResultCode;
import com.lin.shiro.utils.JwtUtil;
import com.lin.utils.Result;
import org.elasticsearch.action.search.SearchResponse;
import org.elasticsearch.index.query.*;
import org.elasticsearch.search.SearchHit;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightBuilder;
import org.elasticsearch.search.fetch.subphase.highlight.HighlightField;
import org.elasticsearch.search.sort.FieldSortBuilder;
import org.elasticsearch.search.sort.SortOrder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.elasticsearch.core.ElasticsearchTemplate;
import org.springframework.data.elasticsearch.core.SearchResultMapper;
import org.springframework.data.elasticsearch.core.aggregation.AggregatedPage;
import org.springframework.data.elasticsearch.core.aggregation.impl.AggregatedPageImpl;
import org.springframework.data.elasticsearch.core.query.NativeSearchQueryBuilder;
import org.springframework.data.elasticsearch.core.query.SearchQuery;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Optional;

/**
 * @program: blog
 * @description: ElasticSearch操作实现类
 * @author: Lin
 * @create: 2020-04-07 18:50
 **/
@Service
public class ElasticSearchServiceImpl implements IElasticSearchService {

    @Autowired
    private ElasticsearchTemplate elasticsearchTemplate;

    @Autowired
    private ArticleCategoryTagsMapper articleCategoryTagsMapper;

    @Autowired
    private ArticleCategoryTagsRepository articleCategoryTagsRepository;

    /**
     * 导入文章数据
     *
     * @return
     */
    @Override
    public Result importArticleData() {
        List<ArticleCategoryTagsVO> list = articleCategoryTagsMapper.listArticleCategoryTagsVO(null);
        if (list.size() != 0) {
            articleCategoryTagsRepository.saveAll(list);
            return Result.successMessage(ElasticSearchConstant.IMPORT_SUCCESS);
        }
        return Result.failureMessage(ElasticSearchConstant.IMPORT_FAILED);
    }

    /**
     * 创建索引
     *
     * @return
     */
    @Override
    public Result createIndex() {

        // 索引存在
        if (elasticsearchTemplate.indexExists(ArticleCategoryTagsVO.class)) {
            return Result.failure(ResultCode.ELASTICSEARCH_INDEX_EXIST);
        }

        // 创建索引，会根据Item类的@Document注解信息来创建
        boolean flag = elasticsearchTemplate.createIndex(ArticleCategoryTagsVO.class);
        // 配置映射，会根据Item类中的id、Field等字段来自动完成映射
        elasticsearchTemplate.putMapping(ArticleCategoryTagsVO.class);
        return Result.success(flag);
    }


    /**
     * 删除索引
     *
     * @return
     */
    @Override
    public Result deleteIndex() {
        // 索引不存在
        if (!elasticsearchTemplate.indexExists(ArticleCategoryTagsVO.class)) {
            return Result.failure(ResultCode.ELASTICSEARCH_INDEX_NO_EXIST);
        }
        boolean flag = elasticsearchTemplate.deleteIndex(ArticleCategoryTagsVO.class);
        return Result.success(flag);
    }

    /**
     * 从es中搜索文章
     *
     * @param searchMap
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public Result searchArticle(Map<String, Object> searchMap, Integer pageNum, Integer pageSize) {
        Pageable pageable = PageRequest.of(pageNum, pageSize);

        /**
         * {
         * 	"query" : {
         * 		"bool":{
         * 			"must":[
         *                 {"match":{"title":"测试"}},
         *                {"match":{"summary":"测试"}},
         *                {"term":{"category.id":2}},
         *                {"terms":{"tags.id":[1]}},
         *                {"range":{"level":{"gte":0}}},
         *                {"range":{"likes":{"gte":0}}},
         *                {"range":{"look":{"gte":0}}},
         *                {"range":{"createTime":{
         * 				"from":"2020-03-27 04:38:40",
         * 				"to":"2020-03-27 04:38:45"}}}
         * 			]
         *        }
         *     }
         * }
         */
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                .withQuery(builderBoolQueryBuilderByMap(searchMap))
                .build();

        // 设置分页
        searchQuery.setPageable(pageable);

        // 查询结果
        Page<ArticleCategoryTagsVO> search = elasticsearchTemplate.queryForPage(
                searchQuery,
                ArticleCategoryTagsVO.class
        );


        return Result.success(search);
    }

    /**
     * 保存ArticleCategoryTagsVO到es中
     * @param articleCategoryTagsVO
     * @return
     */
    @Override
    public Result saveArticleCategoryTagsVO(ArticleCategoryTagsVO articleCategoryTagsVO) {
        if (null == articleCategoryTagsVO){ return Result.failure(ResultCode.PARAM_IS_BLANK);}
        ArticleCategoryTagsVO save = articleCategoryTagsRepository.save(articleCategoryTagsVO);
        return Result.success(save);
    }

    /**
     * 删除es中id相等的数据
     * @param id
     * @return
     */
    @Override
    public Result deleteArticleCategoryTagsVOById(Long id) {
        articleCategoryTagsRepository.deleteById(id);
        return Result.success();
    }

    /**
     * 修改es中id相等的ArticleCategoryTagsVO
     * @param id
     * @param articleCategoryTagsVO
     * @return
     */
    @Override
    public Result updateArticleCategoryTagsVO(Long id, ArticleCategoryTagsVO articleCategoryTagsVO) {
        if(null == articleCategoryTagsVO.getId()){
            articleCategoryTagsVO.setId(id);
        }

        ArticleCategoryTagsVO articleCategoryTagsVOByES = articleCategoryTagsRepository.findById(id).get();


        // 反射将 articleCategoryTagsVO 的值 设置到 articleCategoryTagsVOByES
        Field [] fields = ArticleCategoryTagsVO.class.getDeclaredFields();
        for (Field field: fields) {

            try {
                // 设置可访问
                field.setAccessible(true);

                // 获取到 serialVersionUID 跳过
                if ("serialVersionUID".equals(field.getName())) {
                    continue;
                }

                // 获取articleCategoryTagsVO对象的值
                Object value = field.get(articleCategoryTagsVO);

                // 空指针 与 静态属性 常量属性不放行
                if (null != value && !Modifier.isFinal(field.getModifiers()) && !Modifier.isStatic(field.getModifiers())) {
                    // 将articleCategoryTagsVO获取到的指设置到articleCategoryTagsVOByES
                    field.set(articleCategoryTagsVOByES, value);
                }
                // 设置不可访问
                field.setAccessible(false);
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }


        ArticleCategoryTagsVO update = articleCategoryTagsRepository.save(articleCategoryTagsVOByES);
        return Result.success(update);
    }


    /**
     * 从es中搜索文章 通过关键字
     * https://www.cnblogs.com/shihaiming/p/11062809.html
     *
     * @param keyword  关键字
     * @param pageNum
     * @param pageSize
     * @return
     */
    @Override
    public Result searchArticleByKeyWord(String keyword, String token, Integer pageNum, Integer pageSize) {

        // 启动分页
        Pageable pageable = PageRequest.of(pageNum, pageSize);


        // 查询字段
        String[] fields = new String[]{
                ArticleCategoryTagsConstant.TITLE,
                ArticleCategoryTagsConstant.SUMMARY,
                ArticleCategoryTagsConstant.CATEGORY_NAME,
                ArticleCategoryTagsConstant.TAGS_NAME,
        };


        // 设置查询条件
        SearchQuery searchQuery = new NativeSearchQueryBuilder()
                // 查询字段
                .withQuery(setQueryBuilderByKeyWord(keyword, fields, token))
                // 高亮字段
                .withHighlightFields(setHighlightFields())
                // 设置排序字段
                .withSort(setFieldSortBuilder())
                .build();

        // 设置分页
        searchQuery.setPageable(pageable);

        Page<ArticleCategoryTagsVO> search = null;


        search = elasticsearchTemplate.queryForPage(searchQuery, ArticleCategoryTagsVO.class);


        return Result.success(search);
    }

    /**
     * 构建查询条件
     *
     * @param keyword
     * @param fields
     * @param token
     * @return
     */
    private QueryBuilder setQueryBuilderByKeyWord(String keyword, String[] fields, String token) {
        BoolQueryBuilder boolQueryBuilder = new BoolQueryBuilder();
        boolQueryBuilder.must(
                QueryBuilders.multiMatchQuery(keyword, fields).analyzer("ik_max_word").operator(Operator.OR)
        );

        if(!StringUtils.isEmpty(token)) {

            String author = JwtUtil.getUserName(token);

            if (!StringUtils.isEmpty(author) && !UserConstant.SUPER_ADMIN.equals(author)) {
                boolQueryBuilder.must(QueryBuilders.matchQuery(ArticleCategoryTagsConstant.AUTHOR, author));
            }
        }

        return boolQueryBuilder;
    }


    /**
     * 设置高亮字段
     *
     * @return
     */
    private HighlightBuilder.Field setHighlightFields() {
        // google的色值
        String preTag = "<font color='#dd4b39'>";
        String postTag = "</font>";
        return new HighlightBuilder.Field(ArticleCategoryTagsConstant.TITLE)
                .preTags(preTag)
                .postTags(postTag);
    }

    /**
     * 替换高亮字段
     *
     * @return
     */
    private SearchResultMapper replaceHighlightFields() {
        return new SearchResultMapper() {
            @Override
            public <T> AggregatedPage<T> mapResults(SearchResponse searchResponse, Class<T> aClass, Pageable pageable) {
                List<T> chunk = new ArrayList<>();

                for (SearchHit searchHit : searchResponse.getHits()) {
                    if (searchResponse.getHits().getHits().length <= 0) {
                        return null;
                    }

                    String sourceAsString = searchHit.getSourceAsString();

                    System.out.println("sourceAsString:" + sourceAsString);

                    ArticleCategoryTagsVO articleCategoryTagsVO = JSONObject.parseObject(sourceAsString, ArticleCategoryTagsVO.class);


                    // 替换 title
                    HighlightField title = searchHit.getHighlightFields().get(ArticleCategoryTagsConstant.TITLE);
                    if (title != null) {
                        articleCategoryTagsVO.setTitle(title.fragments()[0].toString());
                    }

                    // 替换 summary
                    HighlightField summary = searchHit.getHighlightFields().get(ArticleCategoryTagsConstant.SUMMARY);
                    if (summary != null) {
                        articleCategoryTagsVO.setSummary(summary.fragments()[0].toString());
                    }

                    // 替换 content
                    HighlightField content = searchHit.getHighlightFields().get(ArticleCategoryTagsConstant.CONTENT);
                    if (content != null) {
                        articleCategoryTagsVO.setContent(content.fragments()[0].toString());
                    }

                    // 替换 categoryName
                    HighlightField categoryName = searchHit.getHighlightFields().get(ArticleCategoryTagsConstant.CATEGORY_NAME);
                    if (categoryName != null) {
                        articleCategoryTagsVO.getCategory().setCategoryName(categoryName.fragments()[0].toString());
                    }

                    chunk.add((T) articleCategoryTagsVO);

                }
                if (chunk.size() > 0) {
                    return new AggregatedPageImpl<>(chunk, pageable, searchResponse.getHits().totalHits);
                }

                return null;
            }

            @Override
            public <T> T mapSearchHit(SearchHit searchHit, Class<T> aClass) {
                return null;
            }
        };
    }


    /**
     * 设置 排序字段
     *
     * @return
     */
    private FieldSortBuilder setFieldSortBuilder() {
        return new FieldSortBuilder(ArticleCategoryTagsConstant.LIKES).order(SortOrder.DESC);
    }


    /**
     * 根据 search 构建 MatchQueryBuilder
     * @param search
     * @return
     */
    /**
     * {
     * "query" : {
     * "bool":{
     * "must":[
     * {"match":{"title":"测试"}},
     * {"match":{"summary":"测试"}},
     * {"term":{"category.id":2}},
     * {"terms":{"tags.id":[1]}},
     * {"range":{"level":{"gte":0}}},
     * {"range":{"likes":{"gte":0}}},
     * {"range":{"look":{"gte":0}}},
     * {"range":{"createTime":{
     * "from":"2020-03-27 04:38:40",
     * "to":"2020-03-27 04:38:45"}}}
     * ]
     * }
     * }
     * }
     */
    private QueryBuilder builderBoolQueryBuilderByMap(Map<String, Object> searchMap) {

        List<QueryBuilder> mustQueryBuilder = new ArrayList();
        if (searchMap.size() != 0) {
            mustQueryBuilder.add(getMatchQueryBuilder(ArticleCategoryTagsConstant.TITLE, searchMap.get(ArticleCategoryTagsConstant.TITLE)));
            mustQueryBuilder.add(getMatchQueryBuilder(ArticleCategoryTagsConstant.SUMMARY, searchMap.get(ArticleCategoryTagsConstant.SUMMARY)));
            mustQueryBuilder.add(getTermQueryBuilder(ArticleCategoryTagsConstant.CATEGORY_ID, searchMap.get(ArticleCategoryTagsConstant.CATEGORY_ID)));
            mustQueryBuilder.add(getTermsQueryBuilder(ArticleCategoryTagsConstant.TAGS_ID, searchMap.get(ArticleCategoryTagsConstant.TAGS_ID)));
            mustQueryBuilder.add(getRangeQueryBuilder(ArticleCategoryTagsConstant.LEVEL, searchMap.get(ArticleCategoryTagsConstant.LEVEL)));
            mustQueryBuilder.add(getRangeQueryBuilder(ArticleCategoryTagsConstant.LIKES, searchMap.get(ArticleCategoryTagsConstant.LIKES)));
            mustQueryBuilder.add(getRangeQueryBuilder(ArticleCategoryTagsConstant.LOOK, searchMap.get(ArticleCategoryTagsConstant.LOOK)));
            mustQueryBuilder.add(getRangeQueryBuilder(ArticleCategoryTagsConstant.CREATE_TIME, searchMap.get(ArticleCategoryTagsConstant.CREATE_TIME)));
        }


        BoolQueryBuilder boolQuery = new BoolQueryBuilder();
        for (QueryBuilder queryBuilder : mustQueryBuilder) {
            if (null != queryBuilder) {
                boolQuery.must(queryBuilder);
            }
        }
        return boolQuery;
    }


    /**
     * 通过key value 获取 MatchQueryBuilder 对象
     *
     * @param key
     * @param value
     * @return
     */
    private QueryBuilder getMatchQueryBuilder(String key, Object value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return new MatchQueryBuilder(key, value);
    }

    /**
     * 通过key value 获取 TermQueryBuilder 对象
     *
     * @param key
     * @param value
     * @return
     */
    private QueryBuilder getTermQueryBuilder(String key, Object value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return new TermQueryBuilder(key, value);
    }


    /**
     * 通过key value 获取 TermQueryBuilder 对象
     *
     * @param key
     * @param value
     * @return
     */
    private QueryBuilder getTermsQueryBuilder(String key, Object value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        return new TermsQueryBuilder(key, value);
    }

    /**
     * 通过key value 获取 TermQueryBuilder 对象
     *
     * @param key
     * @param value
     * @return
     */
    private QueryBuilder getRangeQueryBuilder(String key, Object value) {
        if (StringUtils.isEmpty(value)) {
            return null;
        }
        String[] split = value.toString().split(",");

        RangeQueryBuilder rangeQueryBuilder = new RangeQueryBuilder(key);

        rangeQueryBuilder.from(split[0]);
        if (null != split[1]) {
            rangeQueryBuilder.to(split[1]);
        }

        return rangeQueryBuilder;
    }


}
